//      TITLE("Interval and Profile Clock Interrupts")
//++
//
// Copyright (c) 1990  Microsoft Corporation
//
// Module Name:
//
//    xxclock.s
//
// Abstract:
//
//    This module implements the code necessary to field and process the
//    interval and profile clock interrupts.
//
// Author:
//
//    Chris P. Karamatas 27-Sep-1993
//
//    Based on MIPS version by David N. Cutler (davec) 27-Mar-1990
//
// Modified by:
//
//    Pat Carr           14-Apr-1994      to follow 3.5 model
//    Peter Johnston     30-May-1994      extensive mods for level 612
//    Peter Johnston     10-Jun-1994      updated to level 683 (Beta 2)
//
// Environment:
//
//    Kernel mode only.
//
// Revision History:
//
//--
        .file   "clock.s"

#include "ksppc.h"


//
// Define external variables used by this module.
//

        .extern KeTickCount             // 3 * 4
        .extern KeTimeAdjustment        // 4
        .extern KiAdjustDpcThreshold    // 4
        .extern KiIdealDpcRate          // 4
        .extern KiMaximumDpcQueueDepth  // 4
        .extern KiProfileListHead       // 2 * 4
        .extern KiProfileLock           // 4
        .extern KiTimerTableListHead
        .extern KiTimerExpireDpc
        .extern KiTickOffset
        .extern KeMaximumIncrement

#if !defined(NT_UP) && SPINDBG
        .extern ..KiAcquireSpinLockDbg
#endif


//++
//
// VOID
// KeUpdateSystemTime (
//    IN PKTRAP_FRAME TrapFrame
//    )
//
// Routine Description:
//
//    This routine is entered as the result of an interrupt generated by the
//    interval timer. Its function is to update the system time and check to
//    determine if a timer has expired.
//
//    N.B. This routine is executed on a single processor in a multiprocess
//       system. The remainder of the processors only execute the quantum end
//       and runtime update code.
//
// Arguments:
//
//    TrapFrame     (r.3) - Supplies a pointer to a trap frame.
//    TimeIncrement (r.4) - Supplies the rime increment in 100ns units.
//
// Return Value:
//
//    None.
//
//--

// rem  hal calls this with interrupts disabled

        .set    cr.5.gt, 21
        .set    cr.0.gt, 1

        LEAF_ENTRY(KeUpdateSystemTime)

//
// Update the interrupt time.
//
// N.B. The interrupt time is updated in a very strict manner so that an
// interlock does not have to be used in an MP system.
//

        lwz     r.5,  [toc]KiTickOffset(r.toc)  // get addr of globals used
                                                // from "Compute next tick
        lwz     r.12, [toc]KeMaximumIncrement(r.toc) // offset value."
        lwz     r.7,  KiPcr2 + Pc2InterruptTime + 0(r.0) // get low interrupt time
        lwz     r.8,  KiPcr2 + Pc2InterruptTime + 4(r.0) // get high interrupt time
        lwz     r.10, [toc]KeTickCount(r.toc)
        lwz     r.11, [toc]KiTimerTableListHead(r.toc)

        lwz     r.9,  0(r.5)                    // get tick offset value
        lwz     r.12, 0(r.12)                   // get maximum increment value
        addc    r.7,  r.7, r.4                  // add time increment value
        addze   r.8,  r.8                       // increment high interupt time

        stw     r.8,  KiPcr2 + Pc2InterruptTime + 8(r.0) // store high 2 interrupt time
        stw     r.7,  KiPcr2 + Pc2InterruptTime + 0(r.0) // store low interrupt time
        stw     r.8,  KiPcr2 + Pc2InterruptTime + 4(r.0) // store high 1 interrupt time

        sub.    r.9,  r.9, r.4                  // subtract time increment
                                                // from "Compute next tick
        lwz     r.4,  [toc]KeTimeAdjustment(r.toc)  // offset value."
        stw     r.9,  0(r.5)                    // store tick offset value
        add     r.9,  r.12, r.9                 // add maximum inc to residue
        crmove  cr.5.gt, cr.0.gt                // save cr.0 gt for later
        lwz     r.6,  0(r.10)                   // get low tick count
        bgt     check_timer                     // jif tick not completed

//
// Compute next tick offset value.
//
        stw     r.9,  0(r.5)                    // store tick offset value
        lwz     r.4,  0(r.4)                    // get time adjustment value

//
// Update system time.
//
// N.B. The system time is updated in a very strict manner so that an
// interlock does not have to be used in an MP system.
//

        lwz     r.0,  KiPcr2 + Pc2SystemTime + 0(r.0) // get low system time
        lwz     r.9,  KiPcr2 + Pc2SystemTime + 4(r.0) // get high system time
        addc    r.0,  r.4, r.0                  // add time increment
                                                // From "Update the tick count"
        lwz     r.4,  4(r.10)                   // get high tick count
        addze   r.9,  r.9                       // incremnt high system time

        stw     r.9,  KiPcr2 + Pc2SystemTime + 8(r.0) // store high 2 system time
        stw     r.0,  KiPcr2 + Pc2SystemTime + 0(r.0) // store low system time
        stw     r.9,  KiPcr2 + Pc2SystemTime + 4(r.0) // store high 1 system time
                                                // From "Update the tick count"
        addic   r.9,  r.6, 1                    // increment tick count

//
// Update the tick count.
//
// N.B. The tick count is updated in a very strict manner so that an
// interlock does not have to be used in an MP system.
//

        addze   r.4,  r.4                       // increment high word
        stw     r.9,  KiPcr2 + Pc2TickCountLow(r.0) // store low tick count
        stw     r.4,  8(r.10)                   // store high 2 tick count
        stw     r.9,  0(r.10)                   // store low tick count
        stw     r.4,  4(r.10)                   // store high 1 tick count

//
// Check to determine if a timer has expired at the current (ie old) hand
// value.
//
        rlwinm  r.10, r.6, 3, (TIMER_TABLE_SIZE - 1) << 3 // get table offset
        add     r.10, r.11, r.10                // get addr of table entry
        lwz     r.9,  LsFlink(r.10)             // get addr of 1st timer in list
        cmplw   cr7, r.9,  r.10                 // list empty?
                                                // From "Get the expiration..."
        lwz     r.4,  TiDueTime + TmHighTime - TiTimerListEntry(r.9)
                                                // From "Get the expiration..."
        lwz     r.5,  TiDueTime + TmLowTime  - TiTimerListEntry(r.9)
        beq     cr7, check_next_hand            // jif yes

//
// Get the expiration time from the timer object.
//
// N.B. The offset to the timer list entry must be subtracted out of the
//      displacement calculation.
//
        cmplw   cr.6, r.4, r.8                  // check high time
        cmplw   cr.7, r.5, r.7                  // check low time
        bgt     cr.6, check_next_hand           // this timer has not expired
        blt     cr.6, expire                    // this timer has expired
        ble     cr.7, expire                    // this timer has expired

//
// Check to determine if a timer has expired at the next hand value.
//
check_next_hand:
        addi    r.6,  r.6, 1                    // advance hand entry to next
check_timer:
        rlwinm  r.10, r.6, 3, (TIMER_TABLE_SIZE - 1) << 3 // get table offset
        add     r.10, r.11, r.10                // get addr of table entry
        lwz     r.9,  LsFlink(r.10)             // get addr of 1st timer in list
        cmplw   cr.7, r.9,  r.10                // list empty?
        lwz     r.5,  TiDueTime + TmLowTime  - TiTimerListEntry(r.9)
        beq     cr.7, kust_exit                 // jif yes

// Get the expiration time from the timer object.
//
// N.B. The offset to the timer list entry must be subtracted out of the
//      displacement calculation.
//
                                                // Note we can't move this guy
                                                // above beq kust_exit.
        lwz     r.4,  TiDueTime + TmHighTime - TiTimerListEntry(r.9)
        cmplw   cr.6, r.4, r.8                  // check high time
        cmplw   cr.7, r.5, r.7                  // check low time
        bgt     cr.6, kust_exit                 // this timer has not expired
        blt     cr.6, expire                    // this timer has expired
        bgt     cr.7, kust_exit                 // this timer has not expired

//
// Put timer expiration DPC in the system DPC list and initiate a dispatch
// interrupt on the current processor.
//

expire:
        lwz     r.9,  KiPcr+PcPrcb(r.0)         // get address of PRCB
        lwz     r.10, [toc]KiTimerExpireDpc(r.toc)// get expiration DPC address
        addi    r.11, r.9, PbDpcListHead        // compute DPC listhead address
        addi    r.7,  r.9, PbDpcLock            // compute DPC lock address

#if !defined(NT_UP)
        ACQUIRE_SPIN_LOCK(r.7, r.11, r.0, expire_lock, expire_lock_spin)
#endif

        lwz     r.8,  DpLock(r.10)              // get DPC inserted state
        addi    r.5,  r.10, DpDpcListEntry      // compute addr DPC list entry
        lwz     r.12, LsBlink(r.11)             // get addr last entry in list
        cmplwi  r.8,  0                         // DPC inserted?
        bne     queued                          // jif DPC already inserted
        lwz     r.8,  PbDpcQueueDepth(r.9)      // get DPC queue depth
        stw     r.7,  DpLock(r.10)              // set DPC inserted state
        stw     r.6,  DpSystemArgument1(r.10)   // set timer table hand value

        stw     r.5,  LsBlink(r.11)             // set addr of new last entry
        stw     r.5,  LsFlink(r.12)             // set next in old last entry
        stw     r.11, LsFlink(r.5)              // set address of next entry
        stw     r.12, LsBlink(r.5)              // set address of previous entry

        addi    r.8, r.8, 1                     // increment DPC queue depth
        stw     r.8, PbDpcQueueDepth(r.9)       //

        SOFTWARE_INTERRUPT(DISPATCH_LEVEL, r.4)

queued:

#if !defined(NT_UP)
        RELEASE_SPIN_LOCK(r.7, r.0)
#endif

kust_exit:

        ble     cr.5, ..KeUpdateRunTime         // if lez, full tick

        blr

#if !defined(NT_UP)
        SPIN_ON_SPIN_LOCK(r.7, r.0, expire_lock, expire_lock_spin)
#endif

        DUMMY_EXIT(KeUpdateSystemTime)


//++
//
// VOID
// KeUpdateRunTime (
//    IN PKTRAP_FRAME TrapFrame
//    )
//
// Routine Description:
//
//    This routine is entered as the result of an interrupt generated by the
//    interval timer. Its function is to update the runtime of the current
//    thread, update the runtime of the current thread's process, and decrement
//    the current thread's quantum.
//
//    N.B. This routine is executed on all processors in a multiprocess system.
//
// Arguments:
//
//    TrapFrame (r.3) - Supplies a pointer to a trap frame.
//
// Return Value:
//
//    None.
//
//--

        LEAF_ENTRY(KeUpdateRunTime)

        lwz     r.9,  KiPcr+PcPrcb(r.0)          // get current processor block
        lwz     r.7,  TrMsr(r.3)                 // get saved machine status
                                                 // From "If a DPC is activ..."
        lbz     r.10, TrOldIrql(r.3)             // get previous IRQL
        lwz     r.4,  KiPcr+PcCurrentThread(r.0) // get current thread address
        lwz     r.8, ThUserTime(r.4)             // get user time
        lwz     r.11, PbDpcRoutineActive(r.9)    // get DPC active flag
        extrwi. r.7, r.7, 1, MSR_PR              // test previous mode
        lwz     r.6,  ThApcState + AsProcess(r.4)// get addr current process
        cmpwi   cr.1, r.11, 0                    // DPC active ?
        bne     user                             // jif previous mode was user

//
// If a DPC is active, then increment the time spent executing DPC routines.
// Otherwise, if the old IRQL is greater than DPC level, then increment the
// time spent executing interrupt services routines. Otherwise, increment
// the time spent in kernel mode for the current thread.
//

        lwz     r.11, ThKernelTime(r.4)          // get kernel time
        cmpwi   r.10, DISPATCH_LEVEL             // compare IRQL with DPC level
        addi    r.7,  r.9,  PbInterruptTime      // compute interrupt time addr
        blt     kernel                           // if ltz, inc. thread kernel time
        bgt     intrpt                           // if >DPC level bump interrupt
        addi    r.7,  r.9,  PbDpcTime            // compute DPC time address
        beq     cr.1, kernel                     // jif not dpc active

//
// Update the time spent in DPC/interrupt processing.
//

intrpt:
        lwz     r.8, 0(r.7)              // get processor time
        addi    r.5,  r.9,  PbKernelTime // compute processor kernel time addr.
        addi    r.8, r.8, 1              // increment processor time
        stw     r.8, 0(r.7)              // store processor time
        b       processor

//
// Update the time spent in kernel mode for the current thread.
//

kernel:
        addi    r.6,  r.6,  PrKernelTime  // compute process kernel time addr.
        addi    r.5,  r.9,  PbKernelTime  // compute processor kernel time addr.
        addi    r.11, r.11, 1             // increment kernel time
        stw     r.11, ThKernelTime(r.4)   // store kernel time
        b       continue

//
// Update the time spent in user mode for the current thread.
//

user:
        addi    r.6,  r.6,  PrUserTime    // compute process user time addr.
        addi    r.5,  r.9,  PbUserTime    // compute processor user time addr.
        addi    r.8, r.8, 1               // increment user time
        stw     r.8, ThUserTime(r.4)      // store user time

//
// Update the time spent in kernel/user mode for the current thread's process.
//
// N.B. The update of the process time must be synchronized across processors.
//

        //DBGSTORE_I(r10,r8,0x1112)
continue:
        lwarx   r.10, 0, r.6             // get process time
        addi    r.10, r.10, 1            // increment process time
        stwcx.  r.10, 0, r.6             // store process time
        bne-    continue                 // if store conditional failed

//
// Update the time spent in kernel/user mode for the current processor.
//

processor:
        lwz     r.10, 0(r.5)             // get low processor time

//
// Update the DPC request rate which is computed as the average between
// the previous rate and the current rate.
//

        lwz     r.8, PbDpcQueueDepth(r.9) // get current DPC queue depth
        lwz     r.6, PbDpcLastCount(r.9)  // get last DPC count
        lwz     r.7, PbDpcRequestRate(r.9)// get last DPC request rate
        lwz     r.0, PbDpcInterruptRequested(r.9)// interrupt requested?
                                         // NOTE: we can't move these below
                                         // the next lwz to r.5
        addi    r.10, r.10, 1            // increment processor time
        stw     r.10, 0(r.5)             // store low processor time
        lwz     r.5, PbDpcCount(r.9)      // get current DPC count
        cmpwi   cr.0, r.8, 0
        lwz     r.8, [toc]KiAdjustDpcThreshold(r.toc)
        stw     r.5, PbDpcLastCount(r.9)  // set last DPC count
        sub     r.5, r.5, r.6             // compute count during interval
        cmpwi   cr.7, r.0, 0              // interrupt requested ?
        add     r.5, r.5, r.7             // compute sum of current and last
        srwi    r.5, r.5, 1               // average current and last
        stw     r.5, PbDpcRequestRate(r.9)// set new DPC request rate
        lwz     r.0, 0(r.8)               // get DPC threshold counter
        lwz     r.7, PbMaximumDpcQueueDepth(r.9)// get current max queue depth

//
// If the current DPC queue depth is not zero, a DPC routine is not active,
// and a DPC interrupt has not been requested, then request a dispatch
// interrupt, decrement the maximum DPC queue depth, and reset the threshold
// counter if appropriate.
//

        bne     cr.1, nodpc                     // jif DPC routine active
        beq     cr.0, nodpc                     // jif DPC queue is empty
        bne     cr.7, nodpc                     // jif DPC already requested

        lwz     r.6, [toc]KiIdealDpcRate(r.toc) // get &ideal DPC rate
        stw     r.0, PbAdjustDpcThreshold(r.9)  // set new DPC threshold counter
        lwz     r.6, 0(r.6)                     // get ideal DPC rate
        li      r.0, 1
        stb     r.0, KiPcr+PcDispatchInterrupt(r.0) // request DPC interrupt
        cmpw    cr.6, r.5, r.6                  // current rate < ideal ?
        subic.  r.7, r.7, 1                     // decrement max DPC queue depth
        bge     cr.6, ..KiDecrementQuantum      // jif rate >= ideal
        beq     ..KiDecrementQuantum            // if cur val == 1
        stw     r.7, PbMaximumDpcQueueDepth(r.9)// set new maximum queue depth
        b       ..KiDecrementQuantum

//
// The DPC queue is empty or a DPC routine is active or a DPC interrupt
// has been requested. Count down the adjustment threshold and if the
// count reaches zero, then increment the maximum DPC queue depth, but
// no above the initial value and reset the adjustment threshold value.
//

nodpc:
        lwz     r.5, [toc]KiMaximumDpcQueueDepth(r.toc)
        lwz     r.6, PbAdjustDpcThreshold(r.9)  // get adjustment threshold
        lwz     r.5, 0(r.5)                     // get init max queue depth
        subic.  r.6, r.6, 1                     // decrement adjustment
        stw     r.6, PbAdjustDpcThreshold(r.9)  // threshold counter
        bne     ..KiDecrementQuantum
        cmpw    cr.6, r.5, r.7
        stw     r.0, PbAdjustDpcThreshold(r.9)  // reset threshold
        beq     cr.6, ..KiDecrementQuantum      // jif at max depth
        addi    r.7, r.7, 1                     // increment current max depth
        stw     r.7, PbMaximumDpcQueueDepth(r.9)// set new maximum DPC queue
                                                // depth.



//
// Decrement current thread quantum and check to determine if a quantum end
// has occurred.
//

        ALTERNATE_ENTRY(KiDecrementQuantum)

        lbz     r.5,  ThQuantum(r.4)             // get current thread quantum
        extsb   r.5,  r.5                        // sign-extend thread quantum
        subic.  r.5,  r.5, CLOCK_QUANTUM_DECREMENT // decrement current quantum
        stb     r.5,  ThQuantum(r.4)             // store thread quantum
        bgtlr+                                   // return if quantum remaining

//
// Set quantum end flag and initiate a dispatch interrupt on the current
// processor.
//

        lwz     r.5, PbIdleThread(r.9)           // get address of idle thread
        cmpw    r.5, r.4                         // is this the idle thread?
        beqlr-                                   // return if in idle thread

        stw     r.sp, KiPcr+PcQuantumEnd(r.0)    // set quantum end indicator

        SOFTWARE_INTERRUPT(DISPATCH_LEVEL, r.8)

        LEAF_EXIT(KeUpdateRunTime)

//++
//
// VOID
// KeProfileInterruptWithSource (
//    IN PKTRAP_FRAME TrapFrame,
//    IN KPROFILE_SOURCE ProfileSource
//    )
//
// VOID
// KeProfileInterrupt (
//    IN PKTRAP_FRAME TrapFrame
//    )
//
// Routine Description:
//
//    This routine is entered as the result of an interrupt generated by the
//    profile timer. Its function is to update the profile information for
//    the currently active profile objects.
//
//    N.B. This routine is executed on all processors in a multiprocess system.
//
//    N.B. KeProfileInterruptWithSource currently not implemented
//
// Arguments:
//
//    TrapFrame (r.3) - Supplies a pointer to a trap frame.
//
//    ProfileSource (r.4) - Supplies the source of the profile interrupt
//              KeProfileInterrupt is an alternate entry for backwards
//              compatibility that sets the source to zero (ProfileTime)
//
// Return Value:
//
//    None.
//
//--
        .struct 0
        .space  StackFrameHeaderLength
piLR:   .space  4                               // Link Register
        .align  3                               // 8 byte align
piFrameLength:

        SPECIAL_ENTRY(KeProfileInterrupt)

        li      r.4, 0                          // set profile source to
                                                // ProfileTime

        ALTERNATE_ENTRY(KeProfileInterruptWithSource)

        mflr    r.0
        stwu    r.sp, -piFrameLength(r.sp)
        stw     r.0,  piLR(r.sp)                // save return address

        PROLOGUE_END(KeProfileInterrupt)

#if !defined(NT_UP)

        lwz     r.11, [toc]KiProfileLock(r.toc)

#endif

#if !defined(NT_UP)
        ACQUIRE_SPIN_LOCK(r.11, r.3, r.10, profile_lock, profile_lock_spin)
#endif

        lwz     r.5, KiPcr+PcCurrentThread(r.0) // get current thread address
        lwz     r.5, ThApcState + AsProcess(r.5)// get current process address
        addi    r.5, r.5, PrProfileListHead     // compute profile listhead addr
        bl      ..KiProcessProfileList          // process process profile list
        lwz     r.5, [toc]KiProfileListHead(r.toc)// get profile listhead addr
        bl      ..KiProcessProfileList          // process system profile list

        lwz     r.0, piLR(r.sp)                 // get return address

#if !defined(NT_UP)
        lwz     r.11, [toc]KiProfileLock(r.toc)
        li      r.10, 0
        RELEASE_SPIN_LOCK(r.11, r.10)
#endif

        mtlr    r.0                             // set return address
        addi    r.sp, r.sp, piFrameLength       // deallocate stack frame

        blr

#if !defined(NT_UP)
        SPIN_ON_SPIN_LOCK(r.11, r.10, profile_lock, profile_lock_spin)
#endif

        DUMMY_EXIT(KeProfileInterrupt)

//++
//
// VOID
// KiProcessProfileList (
//    IN PKTRAP_FRAME TrapFrame,
//    IN KPROFILE_SOURCE Source,
//    IN PLIST_ENTRY ListHead
//    )
//
// Routine Description:
//
//    This routine is called to process a profile list.
//
// Arguments:
//
//    TrapFrame (r.3) - Supplies a pointer to a trap frame.
//
//    Source    (r.4) - Supplies profile source to match
//
//    ListHead  (r.5) - Supplies a pointer to a profile list.
//
// Return Value:
//
//    None.
//
// Note:
//
//    Registers r.3 and r.4 are returned unaltered.
//
//--

        LEAF_ENTRY(KiProcessProfileList)

        lwz     r.6,  LsFlink(r.5)              // get address of next entry
        cmplw   r.5,  r.6                       // cmp process profile list head
        beqlr                                   // if eq, end of list
        lwz     r.12, KiPcr+PcPrcb(r.0)         // get address of PRCB
        lwz     r.7,  TrIar(r.3)                // get interrupt PC address
        lwz     r.12, PbSetMember(r.12)         // get current processor num

//
// Scan profile list and increment profile buckets as appropriate.
//

l.10:   lhz     r.0,  PfSource - PfProfileListEntry(r.6)    // get source
        lwz     r.8,  PfRangeBase - PfProfileListEntry(r.6) // get range base
        cmplw   cr.6, r.0,  r.4                             // compare source
        lwz     r.9,  PfRangeLimit - PfProfileListEntry(r.6)// get range limit
        lwz     r.11, PfAffinity - PfProfileListEntry(r.6)  // get affinity
        bne     cr.6, l.20                      // if ne, source mismatch
        cmplw   cr.7, r.7,  r.8                 // check against range base
        cmplw   cr.1, r.7,  r.9                 // check against range limit
        and.    r.11, r.11, r.12                // check affinity
        blt     cr.7, l.20                      // jif less than range base
        bgt     cr.1, l.20                      // jif not less than range limit
        beq     cr.0, l.20                      // jif affinity mismatch
        sub     r.8,  r.7,  r.8                 // compute offset in range
        lwz     r.9,  PfBucketShift - PfProfileListEntry(r.6)// get shift count
        lwz     r.10, PfBuffer - PfProfileListEntry(r.6) // get &profile buffer
        srw     r.8, r.8,  r.9                  // compute bucket offset
        rlwinm  r.8, r.8, 0, 0xfffffffc         // clear low order offset bits
        lwzx    r.7, r.8, r.10                  // increment profile bucket
        addi    r.7, r.7, 1                     //
        stwx    r.7, r.8, r.10                  //
l.20:   lwz     r.6,  LsFlink(r.6)              // get address of next entry
        cmplw   r.5,  r.6                       // more entries in list ?
        bne     l.10                            // jif yes

        LEAF_EXIT(KiProcessProfileList)

